#pragma once
#include <DataCore/sim_data.h>
#include <Dynamics/force_module.h>
#include <Dynamics/Material/PdmMaterial/pdm_material.h>
#include <Dynamics/Pdm/pdm_weight_type.h>
#include <Utilities/SpaceHash/neighbor_list.h>

namespace PhysLeo {

/**
 * Peridynamics cpu model, extends the ForceModule interface.
 * implement the classical peridynamics method for simulation. Note you apply the simplified peridynamcis model by use a default simplified pdm material.
 */
template<typename T>
class PdmCpu : public ForceModule<T> {
public:
    /**
     * empty constructor, set everything to its default value
     */
    PdmCpu();

    /**
     * delete the default copy constructor
     */
    PdmCpu(const PdmCpu<T>&) = delete;

    /**
     * delete the default move constructor
     */
    PdmCpu(PdmCpu<T>&&) = delete;

    /**
     * delete the default copy assignment function
     */
    PdmCpu<T>& operator=(const PdmCpu<T>&) = delete;

    /**
     * delete the default move assignment function
     */
    PdmCpu<T>& operator=(PdmCpu<T>&&) = delete;

    /**
     * default destructor
     */
    virtual ~PdmCpu() = default;

    /**
     * allocate resource.
     * @param[in] sim_data  a data structure manage the simulation data. 
     */
    void allocateResource(SimData<T>& sim_data) override;

    /**
    * return the deformation energy by PDM.
    * @param[in] ptr_pos  current position of the system
    * @return deformation energy according to current position
    */
    T deformEnergy(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos) override;

    /**
    * return the internal force by PDM.
    * @param[in] ptr_pos  current position of the system
    * @param[in] ptr_force  internal force of the system according to current position.
    */
    void internalForce(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos, std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force) override;

protected:
    /**
     * compute the volume dilation for every point according current position
     * @param[in] ptr_pos  current position of the system
     */
    virtual void volumeDilation(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos);

    /**
    * init the ptr_weight_ according to ptr_neighborlist_, ptr_rest_pos_ and pdm_weight_type_
    */
    virtual void initWeights();

    /**
     * initialize the reference matrix Dm according to rest position
     */
    virtual void constructDm();

    //shared resources, will be already set after allocateResource function.

    /**
    * rest position
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_rest_pos_;

    /**
    * current position
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos_;

    /**
     * inverse of reference matrix Dm, use this to compute the deformation gradient F = Ds*Dm^-1
     */
    std::shared_ptr<BufferData<glm::tmat3x3<T>>> ptr_dm_inverse_;

    /**
    * internal force
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force_;

    /**
    * vertex volume
    */
    std::shared_ptr<BufferData<T>> ptr_vert_volume_;

    /**
    * vertex material
    */
    std::shared_ptr<BufferData<T>> ptr_vert_material_;

    //self resources, take care about their allocation and releasion. 

    /**
    * pdm neighbor list info, about neighborhood
    */
    std::shared_ptr<BufferData<NeighborList>> ptr_neighbor_list_;

    /**
     * pdm weighting info
     */
    std::shared_ptr<BufferData<PdmWeight<T>>> ptr_weight_;

    /**
     * pdm volume dilation info
     */
    std::shared_ptr<BufferData<T>> ptr_volume_dilation_;

    /**
    * PDM horizon radius
    */
    T horizon_radius_;

    /**
    * PDM material type 
    */
    PdmMaterialType pdm_material_type_;

    /**
    * whether the material is homogeneous
    */
    bool homogeneous_;

    /**
    * whether PDM handle invert,
    * if pdm_invertibel_ is switch on, then we will use |F*ksi|/|ksi| to replace tau. This will need to estimate deformation gradient F of local area.
    */
    bool pdm_invertible_;

    /**
    * PDM weight type
    */
    PdmWeightType pdm_weight_type_;
};

}